/*******************************************************************************
 * Copyright (c) 2007 University of Oslo.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     University of Oslo - initial API and implementation
 *******************************************************************************/

package com.cea.papyrus.diagram.sequence.sedi.edit.command;

import static java.lang.Math.max;
import static java.lang.Math.min;

import java.util.ArrayList;
import java.util.List;

import org.eclipse.draw2d.geometry.Rectangle;
import org.eclipse.gef.EditPart;
import org.eclipse.gef.GraphicalEditPart;
import org.eclipse.gef.commands.Command;
import org.eclipse.uml2.uml.Interaction;
import org.eclipse.uml2.uml.InteractionOperand;
import org.eclipse.uml2.uml.NamedElement;

import com.cea.papyrus.core.utils.LookForElement;
import com.cea.papyrus.diagraminterchange2.di2.Diagram;
import com.cea.papyrus.diagraminterchange2.di2.GraphNode;

/**
 * Determines the preferred bounds of a combined fragment with respect to its
 * owner.
 * <p>
 * The following attributes must be set before the command can run successfully:
 * </p>
 * <ul>
 * <li>AnyEditPart
 * <li>Diagram
 * <li>Owner
 * <li>Hint - in absolute coordinates
 * </ul>
 * <p>
 * When completed, the following attributes can be read to access the result:
 * </p>
 * <ul>
 * <li>PreferredBounds - in absolute coordinates
 * </ul>
 * 
 * @author frankd@ifi.uio.no
 */
public class DecideCFBoundsCommand extends Command {

	private static final int MIN_WIDTH = 100;
	private static final int MIN_HEIGHT = 50;
	private static final int DEFAULT_WIDTH = 250;
	private static final int DEFAULT_HEIGHT = 130;

	private EditPart anyEditPart;
	private Diagram diagram;
	private Rectangle hint;
	private NamedElement owner;
	private Rectangle preferred;

	public void setAnyEditPart(EditPart editPart) {
		this.anyEditPart = editPart;
	}

	public void setDiagram(final Diagram diagram) {
		this.diagram = diagram;
	}

	public void setHint(final Rectangle hint) {
		this.hint = hint.getCopy();
	}

	public void setOwner(final NamedElement owner) {
		this.owner = owner;
	}

	public Rectangle getPreferredBounds() {
		return preferred;
	}

	@SuppressWarnings("unchecked")
	@Override
	public void execute() {
		Rectangle ownerBounds; // in absolute coords
		if (owner instanceof Interaction) {
			List<GraphNode> list = LookForElement.lookForGraphElementWODiagram(
					owner, diagram, new ArrayList<GraphNode>(1));
			final GraphNode ownerView = list.get(0);
			ownerBounds = new Rectangle(ownerView.getPosition().getCopy(),
					ownerView.getSize().getCopy());

			getEditPartFor(ownerView).getFigure().translateToAbsolute(
					ownerBounds);
		} else { // owner is InteractionOperand
			ownerBounds = OperandUtils.getOperandBounds(diagram,
					(InteractionOperand) owner, anyEditPart);
		}

		int left, width;
		if (owner instanceof Interaction) {
			// Horizontally: CF must fully fit within the interaction.
			// Alternatively, if it exceeds the border, it should do so
			// a fixed amount (10 pixels)

			left = max(ownerBounds.x - 10, hint.x);
			if (left <= ownerBounds.x)
				left -= left - (ownerBounds.x - 10);
			else if (left < ownerBounds.x + 2)
				left = ownerBounds.x + 2;
			else if (left >= ownerBounds.right() - MIN_WIDTH)
				left = max(ownerBounds.x + 2, ownerBounds.right() + 10
						- MIN_WIDTH);
			else if (left > ownerBounds.right() - 2)
				left = max(ownerBounds.x + 2, ownerBounds.right() - 2
						- MIN_WIDTH);

			width = min(ownerBounds.width + 20, hint.width);
			if (width == 0)
				width = between(MIN_WIDTH, ownerBounds.right() + 10 - left,
						DEFAULT_WIDTH);

			if (left + width >= ownerBounds.right())
				width = ownerBounds.right() + 10 - left;
			else if (left + width > ownerBounds.right() - 2)
				width = ownerBounds.right() - 2 - left;
		}

		else {
			// CF must fully fit within the owner
			left = max(ownerBounds.x + 2, hint.x);
			width = min(ownerBounds.width - 2, hint.width);
			if (left + width > ownerBounds.right() - 2)
				width = ownerBounds.right() - 2 - left;
		}

		// Vertically: CF must fully fit within the owner
		int top = max(ownerBounds.y + 23, hint.y); // TODO the value '23'
													// should be
		// calculated
		if (top > ownerBounds.bottom() - 2 - MIN_HEIGHT)
			top = max(ownerBounds.y + 23, ownerBounds.bottom() - 2 - MIN_HEIGHT);

		int height = min(ownerBounds.height - 25, hint.height);
		if (height == 0)
			height = between(MIN_HEIGHT, ownerBounds.bottom() - 2 - top,
					DEFAULT_HEIGHT);

		if (top + height >= ownerBounds.bottom())
			height = ownerBounds.bottom() - 2 - top;

		preferred = new Rectangle(left, top, width, height);
	}

	@Override
	public void redo() {
		execute();
	}

	@Override
	public void dispose() {
		super.dispose();
		owner = null;
		diagram = null;
		hint = null;
		preferred = null;
	}

	/**
	 * Compares the given value to the specified min and max values and returns:
	 * <ul>
	 * <li>min if value &lt; min
	 * <li>value if min &lt; value &lt; max
	 * <li>max if value &gt; max
	 * </ul>
	 */
	private int between(final int min, final int value, final int max) {
		return value < min ? min : (value < max ? value : max);
	}

	private GraphicalEditPart getEditPartFor(final Object view) {
		return (GraphicalEditPart) anyEditPart.getViewer()
				.getEditPartRegistry().get(view);
	}
}
